﻿using System;
using System.Collections;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Xml.Linq;

namespace TripLOA.GUI.PeriodicTripControl
{
    /// <summary>
    /// This User control allow the user to select departure days, departure time
    /// and duration for a periodic trip, ie a trip that repeats itself one or more
    /// days every week. The user can assign a different departure time for each 
    /// day of the week.
    /// </summary>
    public partial class PeriodicTrip : System.Web.UI.UserControl
    {
        #region Costants

        const string DAY_NOT_SCHEDULED = "DayNotScheduled";

        #endregion


        #region Private variables

        // variable associated to the IsValid property
        private bool _isValid;

        // variable associated to the ErrorMessage property
        private string _errorMessage;

        // variable associated to the ScheduledDaysNumber property
        private int _scheduledDaysNumber;

        // variable associated to the Editable property
        private bool _editable;

        // variable associated to the TripDuration property
        private TimeSpan _tripDuration;

        // array of DateTime objects containing the departure time for each day of the week
        private DateTime?[] departures;

        // array of reference to the hidden fields inserted into the markup to keep track of selected days
        private HtmlInputHidden[] daysHiddenField;

        // arrays of reference to the dropdown lists used by user to select departures time
        private DropDownList[] hourDDLs;
        private DropDownList[] minutesDDLs;

        // references to the dropdown lists used by user to select trip duration
        private DropDownList durationMinutesDDL;
        private DropDownList durationHoursDDL;

        #endregion


        #region Constructor

        public PeriodicTrip()
        {
            _isValid = true;
            _errorMessage = "";
            _editable = true;
            _scheduledDaysNumber = 0;
            departures = new DateTime?[7];
            _tripDuration = new TimeSpan();

            daysHiddenField = new HtmlInputHidden[7];
            hourDDLs = new DropDownList[7];
            minutesDDLs = new DropDownList[7];
            
            durationMinutesDDL = null;
            durationHoursDDL = null;
        }

        #endregion


        #region Properties

        #region IsValid
        /// <summary>
        /// Tells if the data submitted by the user are valid.
        /// The value returned by this property is meaningful only after the PageLoad event.
        /// </summary>
        public bool IsValid
        {
            get
            {
                return _isValid;
            }
        }
        #endregion


        #region ErrorMessage
        /// <summary>
        /// Return a string that explains why the data submitted by the user aren't valid.
        /// The value returned by this property is meaningful only if IsValid returns true
        /// after the PageLoad event.
        /// </summary>
        public string ErrorMessage
        {
            get
            {
                return _errorMessage;
            }
        }
        #endregion


        #region ScheduledDaysNumber
        /// <summary>
        /// Returns the number of days scheduled for the trip.
        /// The value returned by this property is meaningful only after the PageLoad event.
        /// </summary>
        public int ScheduledDaysNumber
        {
            get
            {
                return _scheduledDaysNumber;
            }
        }
        #endregion


        #region TripDuration
        /// <summary>
        /// Sets or returns the trip duration.
        /// This property can be read only after the PageLoad event, but you can
        /// set it also in PageLoad, when this event isn't generated by a postback.
        /// </summary>
        public TimeSpan TripDuration
        {
            get
            {
                TimeSpan toReturn = new TimeSpan(_tripDuration.Hours, _tripDuration.Minutes, 0);
                return toReturn;
            }

            set
            {
                _tripDuration = new TimeSpan(value.Hours, value.Minutes, 0);
            }
        }
        #endregion


        #region DeparturesTime
        /// <summary>
        /// Sets or returns a list of scheduled days with the related departure time.
        /// More precisely this property takes or returns an array of 7 position,
        /// where each string element represents the departure time for a day of the week:
        /// 
        /// returnedArray[0] contains the departure time for Sunday
        /// returnedArray[1] contains the departure time for each Monday
        /// ...
        /// returnedArray[6] contains the departure time for each Saturday
        /// 
        /// If returnedArray[i] is equals to "DayNotScheduled", 
        /// it means that the day "i" of the week isn't scheduled for the trip.
        /// 
        /// For scheduled days, returnedArray[i] must have the format 
        /// "hh:mm:ss" or "hh,mm,ss" or "hh.mm.ss", etc..
        /// 
        /// This property can be read only after the PageLoad event, but you can
        /// set it also in PageLoad, when this event isn't generated by a postback.
        /// </summary>
        public string[] DeparturesTime
        {
            get
            {
                string[] toReturn = new string[7];

                for (int i = 0; i < departures.Length; i++)
                {
                    if (departures[i] != null)
                        toReturn[i] = ((DateTime)departures[i]).ToString("HH':'mm':'ss");
                    else
                        toReturn[i] = DAY_NOT_SCHEDULED;
                }

                return toReturn;
            }

            set
            {
                _scheduledDaysNumber = 0;

                for (int i = 0; i < value.Length; i++)
                {
                    if (value[i] != null && !value[i].Equals(DAY_NOT_SCHEDULED))
                    {
                        departures[i] = new DateTime(
                            2008, 1, i + 1, // the date is fictitious
                            int.Parse(value[i].Substring(0, 2)),
                            int.Parse(value[i].Substring(3, 2)),
                            0
                        );

                        _scheduledDaysNumber++;
                    }
                    else
                        departures[i] = null;
                }
            }
        }
        #endregion


        #region Editable
        /// <summary>
        /// Allow or prevent the user from change departure days and time.
        /// This property must be set at every load of the control (eg in PageLoad)
        /// </summary>
        public bool Editable
        {
            get
            {
                return _editable;
            }

            set
            {
                _editable = value;
            }
        }
        #endregion


        #endregion


        #region Deprecated methods

        [Obsolete("Use the DeparturesTime property istead")]
        public string[] GetDeparturesTime()
        {
            return this.DeparturesTime;
        }

        [Obsolete("Use the DeparturesTime property istead")]
        public void SetDeparturesTime(string[] time)
        {
            this.DeparturesTime = time;
        }

        #endregion


        #region Private methods


        #region InitHourDDL
        /// <summary>
        /// Fills each dropdown list that allow the user to select an hour,
        /// with numbers from 0 to 24.
        /// </summary>
        private void InitHourDDL()
        {
            // filling departures time dropdown lists
            for (int i = 0; i < hourDDLs.Length; i++)
            {
                for (int j = 0; j < 24; j++)
                {
                    string hourString = j.ToString("00");

                    ListItem li = new ListItem();
                    li.Text = hourString;
                    li.Value = hourString;

                    hourDDLs[i].Items.Add(li);
                }
            }

            // filling trip duration dropdown list
            for (int k = 0; k < 24; k++)
            {
                string hourString = k.ToString("00");

                ListItem li = new ListItem();
                li.Text = hourString;
                li.Value = hourString;

                durationHoursDDL.Items.Add(li);
            }
        }
        #endregion


        #region InitMinutesDDL
        /// <summary>
        /// Fills each dropdown list that allow the user to select the minutes,
        /// with numbers from 0 to 60 at intervals of 5.
        /// </summary>
        private void InitMinutesDDL()
        {
            // filling departures time dropdown lists
            for (int i = 0; i < minutesDDLs.Length; i++)
            {
                for (int j = 0; j < 60; j += 5)
                {
                    string minutesString = j.ToString("00");

                    ListItem li = new ListItem();
                    li.Text = minutesString;
                    li.Value = minutesString;

                    minutesDDLs[i].Items.Add(li);
                }
            }

            // filling trip duration dropdown list
            for (int k = 0; k < 60; k += 5)
            {
                string minutesString = k.ToString("00");

                ListItem li = new ListItem();
                li.Text = minutesString;
                li.Value = minutesString;

                durationMinutesDDL.Items.Add(li);
            }
        }
        #endregion


        #region LoadUserInput
        /// <summary>
        /// Load scheduled days, departures time and trip duration selected by the user.
        /// Furthermore validates the data submitted by the user.
        /// </summary>
        private void LoadUserInput()
        {
            try
            {
                _scheduledDaysNumber = 0;

                // for each hidden fields inserted into the markup to keep track of selected days...
                for (int i = 0; (i < daysHiddenField.Length) && _isValid; i++)
                {
                    // if the relative day was selected by the user...
                    if (daysHiddenField[i].Value.Equals("1"))
                    {
                        // try to retrieve the departure time

                        int hour = int.Parse(hourDDLs[i].SelectedValue);
                        int minutes = int.Parse(minutesDDLs[i].SelectedValue);

                        if ((hour >= 0) && (hour < 24) && (minutes >= 0) && (minutes < 60) && ((minutes % 5) == 0))
                        {
                            departures[i] = new DateTime(
                                2008, 1, i + 1,
                                hour, minutes, 0
                            );

                            _scheduledDaysNumber++;
                        }
                        else
                            throw new InvalidCastException();

                    }
                    else
                        departures[i] = null;
                }
            }
            catch 
            {
                // in case of error sets the IsValid property to true and
                // the ErrorMessage property to a meaningful message
                _isValid = false;
                _errorMessage = (string)GetLocalResourceObject("errInvalidInput");
            }


            // if there is at least a scheduled day and if there are no invalid input...
            if (_isValid && (_scheduledDaysNumber > 0))
            {
                try
                {
                    // try to retrieve the trip duration

                    int hours = int.Parse(durationHoursDDL.SelectedValue);
                    int minutes = int.Parse(durationMinutesDDL.SelectedValue);

                    if ((hours >= 0) && (hours < 24) && (minutes >= 0) && (minutes < 60) && ((minutes % 5) == 0))
                    {
                        if (hours > 0 || minutes > 0)
                            _tripDuration = new TimeSpan(hours, minutes, 0);
                        else
                        {
                            _isValid = false;
                            _errorMessage = (string)GetLocalResourceObject("errInvalidDuration");
                        }
                    }
                    else
                        throw new InvalidCastException();
                }
                catch 
                {
                    // in case of invalid input, or if trip duration == 00:00,
                    // sets the IsValid property to true and the ErrorMessage 
                    // property to a meaningful message
                    _isValid = false;
                    _errorMessage = (string)GetLocalResourceObject("errInvalidInput");
                }
            }

            // if there is at least a scheduled day and if there are no invalid input, 
            // check for overlapping trips
            if (_isValid && (_scheduledDaysNumber > 0) && CheckForOverlappingTrips())
            {
                // if two or more scheduled trip overlap sets the IsValid property to true and
                // the ErrorMessage property to a meaningful message
                _isValid = false;
                _errorMessage = (string)GetLocalResourceObject("errOverlappingTrips");
            }
        }
        #endregion


        #region CheckForOverlappingTrips
        /// <summary>
        /// Check if two or more scheduled trip overlap.
        /// </summary>
        /// <returns>
        /// True if an overlap was found, false otherwise.
        /// </returns>
        private bool CheckForOverlappingTrips()
        {
            // First checks for overlaps in each couple of consecutive days from Sunday to Saturday
            for (int i = 0; i < (departures.Length -1); i++)
            {
                if ((departures[i] != null) && (departures[i + 1] != null))
                {
                    TimeSpan between2Departures = ((DateTime)departures[i + 1]) - (((DateTime)departures[i]) + _tripDuration);
                    if ((between2Departures.Hours < 0) || (between2Departures.Minutes < 0))
                        return true;
                }
            }

            // Finally checks for an overlap between Saturday and Sunday 
            // (the last and the first day of the week)
            if ((departures[departures.Length - 1] != null) && (departures[0] != null))
            {
                // Creates a fictitious DateTime that represents the first day of the "next" week,
                // with it's departure time. This time is equal to departure time of the first day
                // in the "current" week.
                DateTime firstDayOfTheNextWeek = new DateTime(
                                                2008, 1, departures.Length + 1, // the date is fictitious
                                                ((DateTime)departures[0]).Hour,
                                                ((DateTime)departures[0]).Minute,
                                                0);

                TimeSpan between2Departures = ((DateTime)firstDayOfTheNextWeek) - (((DateTime)departures[departures.Length - 1]) + _tripDuration);
                if ((between2Departures.Hours < 0) || (between2Departures.Minutes < 0))
                    return true;
            }


            return false;
        }
        #endregion


        #endregion


        #region Page events


        #region OnInit
        /// <summary>
        /// Assigns to istance variables/arrays a reference to the related controls defined in the markup
        /// </summary>
        /// <param name="e"></param>
        protected override void OnInit(EventArgs e)
        {
            base.OnInit(e);

            daysHiddenField[0] = ptSundaySelected;
            daysHiddenField[1] = ptMondaySelected;
            daysHiddenField[2] = ptTuesdaySelected;
            daysHiddenField[3] = ptWednesdaySelected;
            daysHiddenField[4] = ptThursdaySelected;
            daysHiddenField[5] = ptFridaySelected;
            daysHiddenField[6] = ptSaturdaySelected;


            hourDDLs[0] = ptSundayHour;
            hourDDLs[1] = ptMondayHour;
            hourDDLs[2] = ptTuesdayHour;
            hourDDLs[3] = ptWednesdayHour;
            hourDDLs[4] = ptThursdayHour;
            hourDDLs[5] = ptFridayHour;
            hourDDLs[6] = ptSaturdayHour;


            minutesDDLs[0] = ptSundayMinutes;
            minutesDDLs[1] = ptMondayMinutes;
            minutesDDLs[2] = ptTuesdayMinutes;
            minutesDDLs[3] = ptWednesdayMinutes;
            minutesDDLs[4] = ptThursdayMinutes;
            minutesDDLs[5] = ptFridayMinutes;
            minutesDDLs[6] = ptSaturdayMinutes;

            durationHoursDDL = ptDurationHours;
            durationMinutesDDL = ptDurationMinutes;
        }
        #endregion


        #region Page_Load
        protected void Page_Load(object sender, EventArgs e)
        {
            if (!Page.IsPostBack)
            {
                // Init dropdown lists
                InitHourDDL();
                InitMinutesDDL();
            }
            else
                LoadUserInput();
        }
        #endregion


        #region OnPreRender
        /// <summary>
        /// Sets the properties of the controls defined in the markup
        /// based on the value of the TripDuration, DeparturesTime and Editable properties.
        /// </summary>
        /// <param name="e"></param>
        protected override void OnPreRender(EventArgs e)
        {
            base.OnPreRender(e);

            // makes possible to access the value of the Enable property from Javascript
            ptEditable.Value = (_editable ? "1" : "0");


            // sets the selected values and the Enabled property of the departures time's dropdown lists
            for (int i = 0; i < departures.Length; i++)
            {
                if (departures[i] != null)
                {
                    hourDDLs[i].SelectedValue = ((DateTime)departures[i]).Hour.ToString("00");
                    minutesDDLs[i].SelectedValue = ((DateTime)departures[i]).Minute.ToString("00");
                    daysHiddenField[i].Value = "1";
                }
                else
                    daysHiddenField[i].Value = "0";

                hourDDLs[i].Enabled = _editable;
                minutesDDLs[i].Enabled = _editable;
            }


            // sets the selected values and the Enabled property of the trip duration's dropdown lists

            durationHoursDDL.SelectedValue = _tripDuration.Hours.ToString("00");
            durationMinutesDDL.SelectedValue = _tripDuration.Minutes.ToString("00");

            durationHoursDDL.Enabled = _editable;
            durationMinutesDDL.Enabled = _editable;

        }
        #endregion


        #endregion
    }
}